/*
	Written By Pradipna Nepal
	www.pradsprojects.com

	Copyright (C) 2012 Prads Projects
	Please read COPYING.txt included along with this source code for more detail.
	If not included, see http://www.gnu.org/licenses/
*/

#include "libParseModule.h"

#define JMP_ADDR_NOT_RESOLVED		0xFFFFFFFF
#define DO_NOT_RESOLVE_JMP			0xFFFFFFFF

#define CONDITION_LOOP				0
#define CONDITION_DECISION			1

bool parseInbuiltCall(const TokenLines *tLine, list<Opcode> *irOut);
void resolveJMPAddr(unsigned int offset, list<Opcode> *opcodeOut, unsigned int label);
void checkCondtionStack(stack<int> *conditionType, list<Opcode> *irOut);

bool ParseModule::parseModule(const vector<TokenModule> *tMod, vector<list<Opcode>> *irOut, const SblTable *symbolTable) {
	unsigned int lineCounter;
	Parser parser;

	for (unsigned int i = 0; i < tMod->size(); ++i) {
		list<Opcode> newIR;
		irOut->push_back(newIR);
		stack<unsigned int> resolveJMPOffset;
		stack<int> conditionType;	//High Word = label, low word = condition type
		lineCounter = 1;

		while (lineCounter < (*tMod)[i].lines.size()) {
			switch ((*tMod)[i].lines[lineCounter].lineType) {
				case TL_ASSIGNMENT:
					parser.setTokenCounter(2);
					if (!parser.parseToIR(&(*irOut)[i], &(*tMod)[i].lines[lineCounter])) return false;
					{
						Opcode temp;
						temp.ir = STM_RY;
						temp.label = 0;
						temp.regs = 0;
						setRegY(temp.regs, 14);
						temp.operand.push_back((*tMod)[i].lines[lineCounter].tokens[0]);
						(*irOut)[i].push_back(temp);
					}
					break;
				case TL_CONDITION:
					if ((*tMod)[i].lines[lineCounter].tokens[0].getDescription() == KW_ELSE) {
						if ((*tMod)[i].lines[lineCounter].tokens[1].getDescription() != KW_IF) {
							parser.pushLabels(false, true);
							break;
						}
						parser.setTokenCounter(2);
					} else {
						if ((*tMod)[i].lines[lineCounter].tokens[0].getDescription() == KW_IF) {
							resolveJMPOffset.push(lineCounter);
							conditionType.push(CONDITION_DECISION);
						} else {
							int loopStart = parser.getCurrentLabel(true);

							resolveJMPOffset.push(DO_NOT_RESOLVE_JMP);
							conditionType.push((loopStart << 16) | CONDITION_LOOP);

							Opcode temp;

							temp.ir = NOP;
							temp.label = loopStart;
							(*irOut)[i].push_back(temp);
						}
						parser.setTokenCounter(1);
					}
					parser.pushLabels(true, true);
					if (!parser.parseToIR(&(*irOut)[i], &(*tMod)[i].lines[lineCounter])) return false;
					{
						Opcode temp;

						temp.ir = CMPI;
						temp.regs = 0;
						temp.operand.push_back(Token("0", TOKEN_DIGIT, DESC_NONE));
						temp.label = 0;
						(*irOut)[i].push_back(temp);

						temp.ir = JX;
						temp.label = parser.getLabelOut(false) << 16;
						(*irOut)[i].push_back(temp);

						temp.ir = NOP;
						temp.label = parser.getLabelIn(true);
						(*irOut)[i].push_back(temp);
					}
					break;
				case TL_CLOSE_SCOPE:
					{
						Opcode temp;
						bool tokenElse = false;

						try {
							if ((*tMod)[i].lines.at(lineCounter + 1).tokens.at(0).getDescription() == KW_ELSE) {
								temp.ir = JMP_HHLL;
								temp.label = JMP_ADDR_NOT_RESOLVED;
								(*irOut)[i].push_back(temp);
								tokenElse = true;
							} else {
								checkCondtionStack(&conditionType, &(*irOut)[i]);
							}
						} catch (out_of_range &e) {
							if (conditionType.size() > 0) checkCondtionStack(&conditionType, &(*irOut)[i]);
						}

						temp.ir = NOP;
						temp.label = parser.getLabelOut(true);
						(*irOut)[i].push_back(temp);

						if (!tokenElse) {
							unsigned int offset = resolveJMPOffset.top();
							resolveJMPOffset.pop();
							if (offset != DO_NOT_RESOLVE_JMP) {
								resolveJMPAddr(offset, &(*irOut)[i], temp.label); 
							}
						}
					}
					break;
				case TL_INBUILT_FUNCTION_CALL:
					if (!parseInbuiltCall(&(*tMod)[i].lines[lineCounter], &(*irOut)[i])) return false;
					break;
				//case TL_FUNCTION_CALL:
					//if (!parseCall(&(*tMod)[i].lines[lineCounter], irOut, symbolTable, &moduleID)) return false;
			}
			parser.reset();
			++lineCounter;
		}
	}

	return true;
}

bool parseInbuiltCall(const TokenLines *tLine, list<Opcode> *irOut) {
	Opcode temp;

	switch (tLine->tokens[0].getDescription()) {
		case KW_BGC_N:
			if (tLine->tokens[1].getToken() == TOKEN_OPENROUNDBRACKET) {
				if (tLine->tokens[2].getToken() != TOKEN_DIGIT) {
					string err = "Calling SetBackground(N) - Expected N as const or literal...";
					printError(ERROR_SYNTAX, err.c_str());
					return false;
				}
				temp.ir = BGC;
				temp.label = 0;
				temp.regs = 0;
				temp.operand.push_back(tLine->tokens[2]);
				irOut->push_back(temp);
			} else {
				string err = "Calling SetBackground(N)";
				printError(ERROR_SYNTAX, err.c_str());
				return false;
			}
			break;
		case KW_PRINT:
			if (tLine->tokens[1].getToken() == TOKEN_OPENROUNDBRACKET) {
				temp.ir = PRNT;
				temp.operand.push_back(tLine->tokens[2]);
				temp.label = 0;
				try {
					temp.regs = atoi(tLine->tokens.at(4).getValue().c_str()) | (atoi(tLine->tokens.at(6).getValue().c_str()) << 16);
				} catch(out_of_range &e) {
					goto printError;
				}
				irOut->push_back(temp);
			} else {
printError:
				string err = "Calling print(ConstString, x, y)";
				printError(ERROR_SYNTAX, err.c_str());
				return false;
			}
			break;
		case KW_DRAW:
			if (tLine->tokens[1].getToken() == TOKEN_OPENROUNDBRACKET) {
				temp.ir = DRAW;
				try {
					temp.operand.push_back(tLine->tokens[2]);
					temp.operand.push_back(tLine->tokens[4]);
					temp.operand.push_back(tLine->tokens[6]);
				} catch (out_of_range &e) {
					goto drawError;
				}
				temp.label = 0;
				temp.regs = 0;
				irOut->push_back(temp);
			} else {
drawError:
				string err = "Calling Draw(Image, x, y)";
				printError(ERROR_SYNTAX, err.c_str());
				return false;
			}
			break;
		case KW_CLS:
			temp.ir = CLS;
			irOut->push_back(temp);
			break;
		case KW_VBLANK:
			temp.ir = VBLNK;
			irOut->push_back(temp);
			break;
		case KW_FLIP:
			if (tLine->tokens.at(1).getToken() == TOKEN_OPENROUNDBRACKET && tLine->tokens.at(2).getToken() == TOKEN_DIGIT) {
				try {
					temp.ir = FLIP;
					temp.operand.push_back(tLine->tokens[2]);
					irOut->push_back(temp);
				} catch(out_of_range &e) {
					goto flipError;
				}
			} else {
flipError:
					string err = "Calling Flip(Orientation)";
					printError(ERROR_SYNTAX, err.c_str());
					return false;
			}
			
	}

	return true;
}

void resolveJMPAddr(unsigned int offset, list<Opcode> *opcodeOut, unsigned int label) {
	list<Opcode>::iterator it = opcodeOut->begin();
	advance(it, offset);

	while (it != opcodeOut->end()) {
		if ((it->ir == JMP_HHLL) && (it->label == JMP_ADDR_NOT_RESOLVED)) {
			it->label = label << 16;
		}
		++it;
	}
}

void checkCondtionStack(stack<int> *conditionType, list<Opcode> *irOut) {
	int cType = conditionType->top();
	conditionType->pop();

	if ((cType & 0xFFFF) == CONDITION_LOOP) {
		Opcode temp;

		temp.ir = JMP_HHLL;
		temp.label = cType & 0xFFFF0000;
		irOut->push_back(temp);
	}
}